package engine.planner;

import java.util.List;

import compiler.absyn.Constant;
import engine.query.Plan;
import engine.query.SelectPlan;
import engine.query.SelectScan;
import engine.query.TablePlan;
import engine.query.TableScan;
import engine.query.UpdateScan;

import static storage.record.Schema.*;
import server.starDB;
import storage.record.Schema;
import storage.tx.Transaction;

public class BasicUpdatePlanner implements UpdatePlanner {

	@Override
	public int executeCreateIndex(CreateIndexData data, Transaction tx) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int executeCreateTable(CreateTableData data, Transaction tx) {
		if (!checkFKeyInit(data, tx)) {
			System.err.println("foreign key check error");
			return 1;
		} else
			starDB.mdMgr().createTable(data.tableName(), data.newSchema(), tx);
		return 0;
	}

	@Override
	public int executeCreateView(CreateViewData data, Transaction tx) {
		starDB.mdMgr().createView(data.viewName(), data.viewDef(), tx);
		return 0;
	}

	@Override
	public int executeDelete(DeleteData data, Transaction tx) {
		Plan p = new TablePlan(data.tableName(), tx);
		p = new SelectPlan(p, data.pred());
		if(!checkReferenced(p, tx)){
			System.err.println("tuple is referenced by a foreign key");
			return 1;
		}
		UpdateScan us = (UpdateScan) p.open();
		int count = 0;
		while(us.next()) {
			us.delete();
			count++;
		}
		us.close();
		return count;
	}

	@Override
	public int executeInsert(InsertData data, Transaction tx) {
		Plan p = new TablePlan(data.tableName(), tx);
		TableScan us = (TableScan) p.open();
		if (checkKeyDup(us, data)) {
			System.err.println("key dup error");
			return 0;
		}
		if (!checkFKeyExist(us, data, tx)) {
			System.err.println("fkey not exist");
			return 0;
		}
		if (checkNull(us, data)) {
			System.err.println("not null error");
			return 0;
		}
		us.beforeFirst();
		us.insert();
		for (String fldname : us.getFieldSet()) {
			Constant val = data.insertInfo.get(fldname);
			us.setVal(fldname, val);
		}
		us.close();
		return 1;
	}

	@Override
	public int executeModify(ModifyData data, Transaction tx) {
		Plan p = new TablePlan(data.tableName(), tx);
		if (!checkFKeyExist((TableScan) p.open(), data, tx)) {
			System.err.println("fkey not exist!");
		}
		p = new SelectPlan(p, data.pred());
		if (!checkReferenced(p,tx)){
			System.err.println("tuple is referenced by a foreign key!");
			return 1;
		}
//		if(!checkTerm(p)){
//			System.out.println("check error!");
//			return 1;
//		}
		SelectScan us = (SelectScan) p.open();

		int count = 0;
		while (us.next()) {
			Constant val = data.newValue().evaluate(us);
			us.setVal(data.targetField(), val);
			count++;
		}
		us.close();
		return count;
	}

	public boolean checkKeyDup(TableScan us, InsertData data) {
		List<String> keyset = us.getKeyFieldSet();
		int flag;
		if (keyset.isEmpty()) {
//			System.out.println("isEmpty");
			return false;
		}
		while (us.next()) {
			flag = 1;
			for (String kf : keyset) {
//				System.out.println("Keyset:"+keyset);
				Constant c = us.getVal(kf);
				Constant v = data.insertInfo.get(kf);
				if (c.equals(v))
					flag = 0;
				else
					flag = 1;
			}
			if (flag == 0) {
				return true;
			}
		}
		return false;
	}

	public boolean checkNull(TableScan us, InsertData data) {
		List<String> nullset = us.getNotNullFieldSet();
		for (String s : nullset) {
			if (data.insertInfo.get(s) == null) {
				return true;
			}
		}
		return false;
	}

	private boolean checkFKeyExist(TableScan us, InsertData data, Transaction tx) {
		List<String> fkeyset = us.getFKeyset();
		if(fkeyset.size()==0){
			return true;
		}
		for (String fldname : fkeyset) {
			Constant v = data.insertInfo.get(fldname);
			String fkeytblname = us.fkeytblname(fldname);
			String fkeyfldname = us.fkeyfldname(fldname);
			TablePlan p = new TablePlan(fkeytblname, tx);
			TableScan s = (TableScan) p.open();
			while (s.next()) {
				Constant c = s.getVal(fkeyfldname);
				if (c.equals(v))
					return true;
			}
		}
		return false;
	}

	private boolean checkFKeyExist(TableScan us, ModifyData data, Transaction tx) {
		List<String> fkeyset = us.getFKeyset();
		Constant v = data.newValue().asConstant();
		for (String fldname : fkeyset) {
			String fkeytblname = us.fkeytblname(fldname);
			String fkeyfldname = us.fkeyfldname(fldname);
			TablePlan p = new TablePlan(fkeytblname, tx);
			TableScan s = (TableScan) p.open();
			while (s.next()) {
				Constant c = s.getVal(fkeyfldname);
				if (c.equals(v))
					return true;
			}
		}
		return false;
	}

	private boolean checkFKeyInit(CreateTableData data, Transaction tx) {
		Schema sch = data.newSchema();
		List<String> fkset = sch.getFKeyset();
		for (String fldname : fkset) {
			String fktblname = sch.fkeytblname(fldname);
			String fkfldname = sch.fkeyfldname(fldname);
			System.out.println(fktblname + "*" + fkfldname);
			TablePlan p = new TablePlan(fktblname, tx);
			Schema s = p.schema();
			if (!s.hasField(fkfldname) || s.iskey(fkfldname) == FALSE)
				return false;
		}
		for (String fldname : fkset) {
			String fktblname = sch.fkeytblname(fldname);
			String fkfldname = sch.fkeyfldname(fldname);
			TablePlan p = new TablePlan(fktblname, tx);
			Schema s = p.schema();
			s.setReferenced(fkfldname, data.tableName(), fldname);
		}
		return true;
	}

	private boolean checkReferenced(Plan p, Transaction tx) {
		Schema sch = p.schema();
		List<String> refset = sch.getRefset();
		if (refset.size() == 0)
			return true;
		for (String fldname : refset) {
			TableScan us = (TableScan) p.open();
			while (us.next()) {
				Constant c = us.getVal(fldname);
				String rktblname = sch.rktblname(fldname);
				String rkfldname = sch.rkfldname(fldname);
				TablePlan tp = new TablePlan(rktblname, tx);
				TableScan ts = (TableScan) tp.open();
				while (ts.next()) {
					Constant v = ts.getVal(rkfldname);
					if (c.equals(v))
						return false;
				}
			}
			us.close();
		}
		return true;
	}
}
